﻿using System.Collections;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService.Symbols;

/// <summary>
///     只读的符号表。此符号表类型是所有符号表的基类。
///     <list type="number">
///         <item>
///             可以通过符号名检索符号定义
///         </item>
///         <item>
///             可以通过标识符检索绑定的符号
///         </item>
///     </list>
/// </summary>
[DebuggerDisplay("Count = {Count}")]
public abstract class ReadOnlySymbolTable : IEnumerable<ISymbol>
{
    /// <summary>
    ///     保存的符号数量
    /// </summary>
    public abstract int Count { get; }


    /// <summary>
    ///     声明符号表的符号，可以是null。
    /// </summary>
    public virtual ISymbol? DomainSymbol => null;


    /// <summary>
    ///     此符号表的父级表，对于最上级的表此属性是<see langword="null" />。
    /// </summary>
    public virtual ReadOnlySymbolTable? Parent => null;


    public abstract IEnumerator<ISymbol> GetEnumerator();


    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }


    /// <summary>
    /// 将指定的模块引入到查找范围内。
    /// </summary>
    /// <param name="module">指定模块符号</param>
    public void ImportModule(ModuleSymbol module)
    {
        ImportModuleCore(module);
    }


    /// <summary>
    ///     使用符号名查找本地符号的内部方法。
    ///     不同的符号表有不同的查找逻辑，符号名是否区分大小写也有所不同。
    ///     宏符号名区分大小写，其他符号不区分大小写。
    /// </summary>
    /// <param name="symbolName">本地符号名</param>
    protected abstract ISymbol? LookupSymbolLocalCore(string symbolName);


    /// <summary>
    ///     使用全限定名查找指定的符号。在查找时，如果在引用模块中查找，需要考虑限定名中是否包含可省略的部分。
    ///     例如，全限定名'ModuleA.ModuleB.ClassA'时在引入'ModuleA'时和'ModuleB.ClassA'时等价的。
    /// </summary>
    /// <param name="qualifiedName">全限定名中的各个部分名，顺序必须和全限定名的前后顺序相同</param>
    protected abstract ISymbol? LookupSymbolDeclareCore(string[] qualifiedName);


    /// <summary>
    ///     在引用模块中查找符号，此符号名不区分大小写。模块符号仅限于声明，在绑定符号时，
    ///     声明符号在查找优先级的最下级。
    /// </summary>
    /// <param name="qualifiedName">符号全限定名</param>
    protected abstract ISymbol? LookupSymbolInImportedModuleCore(string[] qualifiedName);


    /// <summary>
    ///     将指定的模块符号引入到当前符号表中，符号中的成员符号也将在查找符号时被检索到。
    ///     引入模块中符号的被检索优先级仅高于全局声明符号表。
    /// </summary>
    /// <param name="module">被引入的模块符号</param>
    protected abstract void ImportModuleCore(ModuleSymbol module);


    /// <summary>
    ///     判断给定的模块是否已经被引入了。此处用于检索的应当是全限定名。
    /// </summary>
    /// <param name="moduleName">模块全限定名，不区分大小写</param>
    protected abstract bool IsModuleImported(string moduleName);


    /// <summary>
    ///     判断给定名称是否已经在符号表中。此方法不会在预定义符号表中检索。
    /// </summary>
    public abstract bool ContainsName(string name);


    /// <summary>
    ///     查找给定表达式节点所对应的符号。
    /// </summary>
    /// <param name="expression">表达式节点</param>
    public abstract ISymbol? GetSymbolOfExpression(Expression expression);


    /// <summary>
    ///     尝试获取指定符号名的符号，允许使用符号全限定名，全限定名中的各个模块名使用'.'分割。
    ///     如果使用全限定名，将只在声明符号表和引用模块中查找。
    /// </summary>
    /// <param name="fullName">符号的全限定名</param>
    /// <param name="symbol">查找到的符号，如果没有找到，值为<see langword="null" /></param>
    public bool TryGetSymbol(string fullName, [MaybeNullWhen(false)] out ISymbol symbol)
    {
        string[] qualified = fullName.Split('.');
        // 如果全限定名的宽度等于1，优先在本地符号表中查找
        if (qualified.Length == 1)
        {
            symbol = LookupSymbolLocalCore(qualified[0]);
            if (symbol != null)
            {
                return true;
            }
        }

        // 优先在引用模块中查找
        symbol = LookupSymbolInImportedModuleCore(qualified) ?? LookupSymbolDeclareCore(qualified);
        return symbol != null;
    }


    /// <summary>
    ///     使用符号名查找本地符号。此方法不检索引用模块中的符号表。
    /// </summary>
    /// <param name="symbolName">检索的符号名</param>
    /// <param name="lookupParent">
    ///     是否向上查找，如果此参数是<see langword="true" />，将依次向父级符号表查找，直到顶级符号表。
    /// </param>
    public ISymbol? LookupLocalSymbol(string symbolName, bool lookupParent = false)
    {
        ISymbol? symbol = LookupSymbolLocalCore(symbolName);

        if (lookupParent && symbol == null)
        {
            symbol = Parent?.LookupLocalSymbol(symbolName, true);
        }

        return symbol;
    }


    /// <summary>
    ///     使用全限定名查找声明符号。此符号可能是Interface、Class、Module、Enum、Function、Constant。
    /// </summary>
    /// <param name="qualifiedName">符号的全限定名列表</param>
    public ISymbol? LookupDeclaredSymbol(string[] qualifiedName)
    {
        return LookupSymbolDeclareCore(qualifiedName);
    }


    /// <summary>
    ///     使用全限定名查找声明符号。此符号可能是Interface、Class、Module、Enum、Function、Constant。
    /// </summary>
    /// <param name="qualifiedName">符号的全限定名，模块名中间使用'.'分隔</param>
    public ISymbol? LookupDeclaredSymbol(string qualifiedName)
    {
        Debug.Assert(!string.IsNullOrWhiteSpace(qualifiedName));
        string[] fullName = qualifiedName.Split('.');
        return LookupSymbolDeclareCore(fullName);
    }


    /// <summary>
    ///     遍历当前符号表中的符号，每个符号调用一次回调函数。
    /// </summary>
    /// <param name="callback">回调函数</param>
    public void ForEach(Action<ISymbol> callback)
    {
        foreach (ISymbol? symbol in this)
        {
            callback(symbol);
        }
    }


    /// <summary>
    ///     获取当前已有符号组成的列表。
    /// </summary>
    public abstract ImmutableArray<ISymbol> ToImmutable();
}


public static class GlobalSymbols
{
    /// <summary>
    ///     全局声明符号表，此符号表用于保存.d.mrs扩展名文件内的符号定义。
    /// </summary>
    private static ReadOnlySymbolTable? _globalSymbolTable;


    /// <summary>
    ///     获取当前的全局符号表。
    /// </summary>
    public static ReadOnlySymbolTable? GetGlobalDeclareTable()
    {
        return _globalSymbolTable;
    }


    /// <summary>
    ///     设置全局声明符号表。
    /// </summary>
    /// <param name="globalSymbolTable">已有的符号表</param>
    public static void SetGlobalDeclareSymbolTable(ReadOnlySymbolTable globalSymbolTable)
    {
        _globalSymbolTable = globalSymbolTable;
    }


    /// <summary>
    ///     查找全局符号表中的本地定义符号。
    /// </summary>
    /// <param name="symbolName">符号名，不区分大小写</param>
    public static ISymbol? LookupGlobalSymbol(string symbolName)
    {
        // 全局符号表不向上查找
        return GetGlobalDeclareTable()?.LookupLocalSymbol(symbolName);
    }


    /// <summary>
    ///     查找全局符号表中的声明符号。可以使用全限定名，全限定名中间使用'.'分隔。
    /// </summary>
    /// <param name="symbolName">符号名，可以使用全限定名</param>
    public static ISymbol? LookupDeclaredSymbol(string symbolName)
    {
        return GetGlobalDeclareTable()?.LookupDeclaredSymbol(symbolName);
    }


    /// <summary>
    ///     使用全限定名查找全局符号表中的声明符号。
    /// </summary>
    /// <param name="qualifiedName">全限定名</param>
    public static ISymbol? LookupDeclaredSymbol(string[] qualifiedName)
    {
        return GetGlobalDeclareTable()?.LookupDeclaredSymbol(qualifiedName);
    }


    /// <summary>
    ///     尝试获取指定名称的符号。
    /// </summary>
    /// <param name="name">符号名，可以使用全限定名</param>
    /// <param name="symbol">查找到的符号</param>
    public static bool TryGetDeclaredSymbol(string name, [MaybeNullWhen(false)] out ISymbol symbol)
    {
        symbol = null;
        return _globalSymbolTable != null && _globalSymbolTable.TryGetSymbol(name, out symbol);
    }
}